package com.himaci.gtbattle.model;

import com.himaci.framework.adt.Tree;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

/**
 * Kelas untuk mengkalkulasi pergerakan AI
 *
 * @author XNPSDN
 */
public class AI {

    /**
     * Struktur untuk element pembangkitan tree
     */
    public class WorldState {

	Action action;
	World world;

	public WorldState(Action action, World world) {
	    this.action = action;
	    this.world = world;
	}
    }

    /**
     * List aksi
     */
    public class ActionList {

	ArrayList<WorldState> node;
	int payoff1;
	int payoff2;

	public ActionList(ArrayList<WorldState> actions, int payoff1, int payoff2) {
	    this.node = actions;
	    this.payoff1 = payoff1;
	    this.payoff2 = payoff2;
	}
    }
    public static final int NO_AI = 0;
    public static final int LEVEL_EASY = 2;
    public static final int LEVEL_MEDIUM = 3;
    public static final int LEVEL_HARD = 4;
    public static final int LEVEL_ASIAN = 4;
    public Player player;
    public int aiLevel;
    public ArrayList<Action> generatedActions;
    public int currentActionExecuted;

    /**
     * Konstuktor
     */
    public AI(Player player, int aiLevel) {
	this.player = player;
	this.aiLevel = aiLevel;
	generatedActions = new ArrayList<>();
	currentActionExecuted = 0;
    }

    /**
     * Menggenerate aksi
     * @param world World yang ingin digenerate nilainya
     */
    public void generateAction(World world) {
	if (aiLevel == NO_AI) {
	    return;
	}

	Tree<WorldState> tree = new Tree<>(new WorldState(null, world));
	ArrayList<ActionList> actions = new ArrayList<>();

	// generate pohon
	generateLeafs(tree, player.id, aiLevel, actions);

	// dari action list, diseleksi mana yang bagus
	int playerNumber = this.player.id;

	if (aiLevel % 2 == 0) {
	    if (playerNumber == Player.PLAYER_ONE) {
		playerNumber = Player.PLAYER_TWO;
	    } else {
		playerNumber = Player.PLAYER_ONE;
	    }
	}

	// iterasi kedalaman pohon
	ActionList bestActionList = null;
	for (int i = aiLevel - 1; i >= 0; --i) {
	    // buat hashmap untuk nyimpen parent
	    HashMap<WorldState, ActionList> parentMaps = new HashMap<>();
	    ArrayList<ActionList> removeQueue = new ArrayList<>();

	    for (ActionList action : actions) {
		if (i > 0) {
		    if (playerNumber == Player.PLAYER_ONE) {
			if (parentMaps.containsKey(action.node.get(i - 1))) {
			    if (action.payoff1 > parentMaps.get(action.node.get(i - 1)).payoff1) {
				parentMaps.put(action.node.get(i - 1), action);
			    } else {
				removeQueue.add(action);
			    }
			} else {
			    parentMaps.put(action.node.get(i - 1), action);
			}
		    } else {
			if (parentMaps.containsKey(action.node.get(i - 1))) {
			    if (action.payoff2 > parentMaps.get(action.node.get(i - 1)).payoff2) {
				parentMaps.put(action.node.get(i - 1), action);
			    } else {
				removeQueue.add(action);
			    }
			} else {
			    parentMaps.put(action.node.get(i - 1), action);
			}
		    }
		} else {
		    if (playerNumber == Player.PLAYER_ONE) {
			if (bestActionList == null) {
			    bestActionList = action;
			} else if (action.payoff1 > bestActionList.payoff1) {
			    bestActionList = action;
			}
		    } else {
			if (bestActionList == null) {
			    bestActionList = action;
			} else if (action.payoff2 > bestActionList.payoff2) {
			    bestActionList = action;
			}
		    }
		}
	    }

	    // hapus yang sudah masuk queue
	    for (ActionList action : removeQueue) {
		actions.remove(action);
	    }

	    // ganti perhitungan
	    if (playerNumber == Player.PLAYER_ONE) {
		playerNumber = Player.PLAYER_TWO;
	    } else {
		playerNumber = Player.PLAYER_ONE;
	    }
	}

	generatedActions.clear();
	// masukkin aksi yang bener
	for (WorldState action : bestActionList.node) {
	    generatedActions.add(action.action);
	}
	currentActionExecuted = 0;

	// print estimated moves
	System.out.println("* Generate Action");
	System.out.println("* Estimated Payoff = " + bestActionList.payoff1 + "," + bestActionList.payoff2);
	System.out.println("* Estimated Moves :");
	for (Action action : generatedActions) {
	    // print aksi yang akan dilakukan	    
	    if (action != null) {
		System.out.println("** " + action.display());
	    } else {
		System.out.println("** Skip Skill");
	    }
	}
    }

    /**
     * Menggenerate leaf secara rekursif
     * @param tree Pohon
     * @param playerTurn ID player gilirannya
     * @param depth Sisa kedalaman pohon
     */
    public void generateLeafs(Tree<WorldState> tree, int playerTurn, int depth, ArrayList<ActionList> actions) {
	if (depth <= 0) {
	    return;
	}

	// best payoff dicatat apabila di daun terakhir
	WorldState bestPayoff = null;

	for (Action action : Action.getInstance().getActionList(playerTurn)) {
	    World temporaryWorld = tree.head.world.copy();

	    if (action.canExecute(temporaryWorld)) {
		//System.out.println(depth + " : " + action.display());
		action.execute(temporaryWorld);
		temporaryWorld.updateDead();

		temporaryWorld.advance();
		temporaryWorld.updateDead();

		if (depth > 1) {
		    WorldState temporaryWorldState = new WorldState(action, temporaryWorld);
		    tree.addLeaf(temporaryWorldState);
		} else {
		    // dicatat best payoffnya
		    if (bestPayoff == null) {
			WorldState temporaryWorldState = new WorldState(action, temporaryWorld);
			bestPayoff = temporaryWorldState;
		    } else {
			if (getPayoff(temporaryWorld, playerTurn) > getPayoff(bestPayoff.world, playerTurn)) {
			    WorldState temporaryWorldState = new WorldState(action, temporaryWorld);
			    bestPayoff = temporaryWorldState;
			}
		    }
		}
	    }
	}

	World temporaryWorld = tree.head.world.copy();

	temporaryWorld.advance();
	temporaryWorld.updateDead();

	if (depth > 1) {
	    WorldState temporaryWorldState = new WorldState(null, temporaryWorld);
	    tree.addLeaf(temporaryWorldState);
	} else {
	    // dicatat best payoffnya
	    if (bestPayoff == null) {
		WorldState temporaryWorldState = new WorldState(null, temporaryWorld);
		bestPayoff = temporaryWorldState;
	    } else {
		if (getPayoff(temporaryWorld, playerTurn) > getPayoff(bestPayoff.world, playerTurn)) {
		    WorldState temporaryWorldState = new WorldState(null, temporaryWorld);
		    bestPayoff = temporaryWorldState;
		}
	    }
	}

	// kalo depthnya udah 1, di add ke action list
	if (depth == 1) {
	    //System.out.println(tree.toString() + bestPayoff.action.display() + " : " + getPayoff(bestPayoff.world, 1) + " " + getPayoff(bestPayoff.world, 2));
	    tree.addLeaf(bestPayoff);

	    // buat action listnya
	    ActionList actionsList = new ActionList((ArrayList<WorldState>) tree.getPredecessor(bestPayoff),
		    getPayoff(bestPayoff.world, Player.PLAYER_ONE),
		    getPayoff(bestPayoff.world, Player.PLAYER_TWO));
	    Collections.reverse(actionsList.node);
	    actionsList.node.remove(0);
	    actionsList.node.add(bestPayoff);

	    // masukkin ke kontainer
	    actions.add(actionsList);
	}

	// sudah dieksekusi, kurangi depth
	depth--;

	// rekursif disini
	if (depth > 0) {
	    if (playerTurn == Player.PLAYER_ONE) {
		playerTurn = Player.PLAYER_TWO;
	    } else if (playerTurn == Player.PLAYER_TWO) {
		playerTurn = Player.PLAYER_ONE;
	    }
	    // kembangan pohon
	    for (Tree leaf : tree.leafs) {
		generateLeafs(leaf, playerTurn, depth, actions);
	    }
	}
    }

    /**
     * Menghitung nilai payoff dari world
     * @param world World yang ingin dihitung nilainya
     * @param playerId Id player
     * @return Nilai payoff
     */
    public static int getPayoff(World world, int playerId) {
	// assign player yang ingin dihitung
	Player currentPlayer;
	Player enemyPlayer;
	if (playerId == Player.PLAYER_ONE) {
	    currentPlayer = world.player1;
	    enemyPlayer = world.player2;
	} else {
	    currentPlayer = world.player2;
	    enemyPlayer = world.player1;
	}

	// variabel payoff
	int retVal = 0;
	// total vitalitas yang berkurang dari seluruh ksatria dalam tim lawan
	for (int i = 0; i < 3; ++i) {
	    retVal += enemyPlayer.characters[i].maxVitality - enemyPlayer.characters[i].currVitality;
	}
	// total vitalitas yang tersisa dari tim sendiri
	for (int i = 0; i < 3; ++i) {
	    retVal += currentPlayer.characters[i].currVitality;
	}
	// jumlah ksatria yang mati di tim lawan * konstanta
	final int KONSTANTA = 100000;
	for (int i = 0; i < 3; ++i) {
	    if (enemyPlayer.characters[i].isDead()) {
		retVal += KONSTANTA;
	    }
	}
	// total attack dan defense tim sendiri
	for (int i = 0; i < 3; ++i) {
	    retVal += currentPlayer.characters[i].getAttack();
	    retVal += currentPlayer.characters[i].currDefense;
	}

	return retVal;
    }
}
